Skip to content

vapor_split: report events and v-model as bindings (0.16.0) - #6

Merged
dannote merged 1 commit into
mainfrom
generic-vapor-split
Oct 1, 2026
Merged

dannote merged 1 commit into
mainfrom
generic-vapor-split

Conversation

@dannote

@dannote dannote commented Oct 1, 2026

Copy link
Copy Markdown
Member

For vize 0.16.0. This takes LiveView's conventions out of vapor_split, so vize_ex stays a binding to Vize and PhoenixVapor owns how events are rendered.

Before

vapor_split wrote LiveView attributes into the statics:

Vize.vapor_split!(~s(<button @click="save">{{ label }}</button>)).statics
# ["<button phx-click=\"save\">", "</button>"]

v-model also added phx-change="<name>_changed", a naming convention of PhoenixVapor's. Callers couldn't opt out: PhoenixVapor's hybrid mode, where Vue in the browser handles events, still got phx-click attributes, which LiveView acts on until the client mounts.

After

Events and v-models are left unrendered and returned as bindings:

{:ok, split} = Vize.vapor_split(~s(<button @click="save">{{ label }}</button>))

split.statics   # ["<button>", "</button>"]
split.bindings  # [%{kind: :set_event, node: %{key: {:static_, "click"}, value: "save", ...}, at: {0, 7}}]
  • node is the Vapor IR node, encoded exactly as vapor_ir/1 does: a SetEventIRNode, with its modifiers, or the v-model DirectiveIRNode.
  • at is {static_index, byte_offset}: where the element's start tag ends, so the caller can insert attributes there without parsing HTML.
  • Every nested v-if and v-for split carries its own :bindings.

Binding markers go through the same marker pass as slots, so positions are exact even when slots come before them in the same tag or static.

Breaking

vapor_split's output changes, so this is 0.16.0. PhoenixVapor is the only known consumer, and its adaptation is ready on a branch.

Volt 0.19 requires vize ~> 0.15.0. A Volt release accepting 0.16 is needed before PhoenixVapor, which requires Volt, can move to it.

Verification

VIZE_EX_BUILD=1 mix ci passes: lint (including rustq.gen --check, clippy and Dialyzer) and 122 tests. New tests cover:

  • event bindings with modifiers;
  • v-model bindings;
  • positions in a later static;
  • bindings inside v-if and v-for blocks.

rustq.gen --check also passes under MIX_ENV=test.

vapor_split wrote LiveView's conventions into its output: events became
phx-* attributes and v-model added phx-change="<name>_changed". That is
PhoenixVapor's policy, not Vize's, and it left callers no way to choose
otherwise (PhoenixVapor's hybrid mode needs no event attributes at all).

The split now leaves events and v-models unrendered and returns them as
bindings: the IR node, as vapor_ir encodes it, and the
{static_index, offset} where the element's start tag ends, so a caller
can insert its own attributes there. Binding markers go through the
same marker pass as slots, so positions stay exact across slots, and
every nested v-if and v-for split carries its own bindings.

This changes vapor_split's output and is meant for 0.16.0.
@dannote
dannote merged commit 8840788 into main Oct 1, 2026
2 checks passed
@dannote
dannote deleted the generic-vapor-split branch October 3, 2026 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant